from collections import namedtuple
import string
import re

class ParserTemplate:
	Token = namedtuple('Token', ['page', 'panel', 'text', 'line'])
	
	class EofError(Exception): pass
	
	class ParserEndError(Exception): pass
	
	def __init__(self, file):
		self._file = file
		self._cur_line = -1 # start is 0 based
		self._next_line()
		self._token_gen = self._token_generator()

	def __del__(self):
		self._file.close()
	
	def next_token(self):
		return next(self._token_gen)
	
	def _token_generator(self):
		# first call passes a dummy node as last_token to parser
		last_token = ParserTemplate.Token("1", "1", "dummy start node", 0)
		while last_token is not None:
			tokens = self._parse_next(last_token)
			for token in tokens:
				yield token
			last_token = tokens[-1]
		# after last token has been parsed return None indefinitely
		while True: yield None
	
	def _readline(self):
		line = self._file.readline()
		self._cur_line += 1
		if not line: raise self.EofError()
		return line.strip()
	
	def _next_line(self):
		try:
			line = self._readline()
			while not line:
				line = self._readline()
			self._line = line
		except self.EofError:
			# we don't forward the exception here, instead we only
			# throw the exception when someone tries to read the line
			self._line = None
			
	def _get_line(self):
		if self._line is None: raise self.ParserEndError()
		return self._line
	
	def _parse_re(self, regex):
		match = regex.match(self._get_line())
		if match:
			self._next_line()
			return match.group(1).strip()
		return None

	@staticmethod
	def _remove_japanese_chars(text):
		""" Removes japanese characters at the beginning of the text"""
		# pos is set here to avoid an error if text is empty
		pos = 0
		text = re.sub("<3", "", text)
		for pos, ch in enumerate(text):
			if ch in string.whitespace:
				continue
			# presumably:
			# hiragana: 0x3040 <= c <= 0x309f resp. 0x1b000 <= c <= 0x1b0ff
			# cjk 0x4e00 <= c <= 0x9fff
			# but those ranges don't seem correct and in the end
			# anything above 2027 (= ...) shouldn't appear in the translation
			if ord(ch) > 0x2027:
				continue
			break
		return text[pos:].strip()
	
	def _parse_next(self, last_token):
		""" Implemented by actual parser. last_token is the last token that was returned by parser
		Must return a list of new tokens. On first call a dummy node with page=1, panel=1 is returned.
		To signal the end of the file return a list that contains None as its last element"""
		raise NotImplementedError("You have to implement this function in your subclass")
